#include <iostream>
#include "46customTime.hpp"
// 友元
// 通常,公有函数是访问C++私有部分的唯一途径,但是这种设计太过严格,以至于不适合特定的编程问题
// 在这种情况下: C++提供了另外一种形式的访问权限: 友元

// 友元有3种:
//         友元函数:
//         友元类:
//         友元成员函数:
// 通过使函数成为类的友元,可以赋予函数与类的成员函数相同的访问权限

// 这里先介绍友元函数,后续介绍友元类和友元成员函数
// 前面我们提到了一个原理: A*B = A.operator* (B);
// 按照道理来说 B * 2.3 = 2.3 * B; 但是实际上不是这样,后面的2.3是double类型变量, 而double本身没有重载运算符
// 解决方式: 1. 当然可以选择: 只使用 B * 2.3 这样的写法
//          2. 还有另一种解决方式,使用非成员函数(大多数运算符都可以通过成员或者非成员函数重载),就像下面的方式,通过编译器进行识别:
//            A = 2.2 * B;
//            等同于:
//            A = operator(2.2, B);         函数原型是:  Time operator *(double num,Time &t);
// 但是类似上面的写法又引出了这样的一个问题: 非成员函数不能直接访问类的私有数据,而非成员函数友元函数可以方位类的私有成员

// 创建非成员函数友元函数(创建友元)

// 要想创建友元函数,第一步:添加原型到类声明中 并使用关键字friend表示友元函数 这里添加到了 ./46customTime.cpp/hpp中
// friend CustomTime operator *(double num, const CustomTime &ct);
// 这个原型意味着下面的两点: 1. operator *()函数是在类函数声明的,但是他不是成员函数,因此不能通过成员运算符调用(比如: A.operator*(double, Class))
//                         2. operator *()函数不是成员函数,但是他和成员函数的访问权限相同
// 第二步: 实现这个函数,注意如果编译提示不可访问私有属性,请检查你的形参是否与头文件中声明的函数形参一致
CustomTime operator*(double num, const CustomTime &ct)
{
    CustomTime result;
    long totalMinutes = ct.hours * num * 60 + ct.minutes * num;
    result.hours = totalMinutes / 60;
    result.minutes = totalMinutes % 60;
    return result;
}
// 这样写之后,语句 A = 2.75 * B 将会被编译器翻译成 A = operator(2.75,B); 并且正确执行

// 有人会说 友元是不是有悖于OOP(面向对象编程)的封装性呢?
// 因为类声明控制了哪一个函数是友元,类方法和友元只是表达接口的两种不同的机制

// 常用的友元: 重载<<运算符
// 我们经常使用ostream中的cout对象的<<运算符进行输出操作,其实cout中的<<运算符也是重载运算符,
// 之前我们说他是一个"智能"的对象,能够识别类型进行输出,就是因为cout的<<运算符将所有的类型全部使用运算符重载,以进行不同的输出操作
// 之所以可以这样做是因为 << 运算符是C++中可以被重载的运算符之一

// 总之,要使用cout识别CustomTime,应该说要使CustomTime类知道使用cout,必须使用友元函数;
// 就像下面的这样:
// cout << time;
// 但是这样翻译成 cout.operator <<(time); 显然我们不应该修改ostream中的内容,来达到目的,因为这样做会在标准接口上浪费时间

// 这里可以这样使用友元函数
// void operator << (std::ostream & os, const CustomTime & t){
//     os << t.hours << "hours, " << t.minutes << "minutes." ;
// }
// 这样就实现了 cout << CustomTime 这种格式
// 但是你会发现一个问题: cout << "time is:" << CustomTime << " (Tuesday)\n"; 不能正常工作

// 原因是友元没有返回ostream引用, 导致翻译成 operator<<(cout, string) -> 返回cout对象  调用operaator<<(cout, CustomTime) 返回void ->  string???(无法调用!)
// 将方法修改成下面返回ostream对象即可:
std::ostream &operator<<(std::ostream &os, const CustomTime &t)
{
    os << t.hours << "hours, " << t.minutes << "minutes.";
}
// 最终建议这样使用友元函数
// 注意: 只有在类声明的时候需要使用friend关键字,除非函数定义的也是原型,否则不能在函数定义中使用 friend 关键字
// 而且友元函数不需要使用 CustomTime::限定符


// 重载运算符: 作为成员函数还是非成员函数
// 友元函数会和运算符重载冲突,请看下面的例子:
// CustomTime operator*(const CustomTime& ct) const;
// friend operator*(const CustomTime & ct1,const CustomTime & ct2);
// 你会发现上面两个声明的调用和实现方式相同, 都是  CustomTime1 * CustomTime2;
// 上面的两种声明相互冲突!! 同时定义上面的两种格式将会被视为二义性,导致编译错误

// 那么使用哪一种格式更好呢?
// 后面再讲!
